home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2009 February / PCWFEB09.iso / Software / Linux / Kubuntu 8.10 / kubuntu-8.10-desktop-i386.iso / casper / filesystem.squashfs / usr / share / pyshared / softwareproperties / SoftwareProperties.py < prev   
Text File  |  2008-10-15  |  23KB  |  594 lines

  1. #  software-properties backend
  2. #
  3. #  Copyright (c) 2004-2007 Canonical Ltd.
  4. #                2004-2005 Michiel Sikkes
  5. #
  6. #  Author: Michiel Sikkes <michiel@eyesopened.nl>
  7. #          Michael Vogt <mvo@debian.org>
  8. #          Sebastian Heinlein <glatzor@ubuntu.com>
  9. #
  10. #  This program is free software; you can redistribute it and/or
  11. #  modify it under the terms of the GNU General Public License as
  12. #  published by the Free Software Foundation; either version 2 of the
  13. #  License, or (at your option) any later version.
  14. #
  15. #  This program is distributed in the hope that it will be useful,
  16. #  but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  18. #  GNU General Public License for more details.
  19. #
  20. #  You should have received a copy of the GNU General Public License
  21. #  along with this program; if not, write to the Free Software
  22. #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
  23. #  USA
  24.  
  25. import apt_pkg
  26. import md5
  27. import re
  28. import os
  29. import sys
  30. import shutil
  31. from tempfile import NamedTemporaryFile
  32. import string
  33. from xml.sax.saxutils import escape
  34. from ConfigParser import ConfigParser
  35. from gettext import gettext as _
  36.  
  37. from AptAuth import AptAuth
  38. import softwareproperties
  39. import aptsources
  40. import aptsources.distro
  41. from aptsources.sourceslist import SourcesList, SourceEntry
  42.  
  43.  
  44.  
  45. class SoftwareProperties:
  46.  
  47.   # release upgrades policy
  48.   RELEASE_UPGRADES_CONF = "/etc/update-manager/release-upgrades"
  49.   #RELEASE_UPGRADES_CONF = "/tmp/release-upgrades"
  50.   (
  51.     RELEASE_UPGRADES_NEVER,
  52.     RELEASE_UPGRADES_NORMAL,
  53.     RELEASE_UPGRADES_LTS
  54.   ) = range(3)
  55.   release_upgrades_policy_map = {
  56.     RELEASE_UPGRADES_NEVER  : 'never',
  57.     RELEASE_UPGRADES_NORMAL : 'normal',
  58.     RELEASE_UPGRADES_LTS    : 'lts',
  59.   }
  60.   
  61.   def __init__(self, datadir=None, options=None):
  62.     """ Provides the core functionality to configure the used software 
  63.         repositories, the corresponding authentication keys and 
  64.         update automation """
  65.     self.popconfile = "/etc/popularity-contest.conf"
  66.  
  67.     # FIXME: some saner way is needed here
  68.     if datadir == None:
  69.       datadir = "/usr/share/software-properties/"
  70.     self.datadir = datadir
  71.  
  72.     self.sourceslist = SourcesList()
  73.     self.distro = aptsources.distro.get_distro()
  74.  
  75.     self.seen_server = []
  76.     self.modified_sourceslist = False
  77.  
  78.     self.reload_sourceslist()
  79.     self.backup_sourceslist()
  80.  
  81.     self.backup_apt_conf()
  82.  
  83.     # FIXME: we need to store this value in a config option
  84.     #self.custom_mirrors = ["http://adasdwww.de/ubuntu"]
  85.     self.custom_mirrors= []
  86.  
  87.     # apt-key stuff
  88.     self.apt_key = AptAuth()
  89.  
  90.   def backup_apt_conf(self):
  91.     """Backup all apt configuration options"""
  92.     self.apt_conf_backup = {}
  93.     for option in softwareproperties.CONF_MAP.keys():
  94.         value = apt_pkg.Config.FindI(softwareproperties.CONF_MAP[option])
  95.         self.apt_conf_backup[option] = value
  96.  
  97.   def restore_apt_conf(self):
  98.     """Restore the stored apt configuration"""
  99.     for option in self.apt_conf_backup.keys():
  100.         apt_pkg.Config.Set(softwareproperties.CONF_MAP[option],
  101.                            str(self.apt_conf_backup[option]))
  102.     self.write_config()
  103.  
  104.   def get_update_automation_level(self):
  105.     """ Parse the apt cron configuration. Try to fit a predefined use case 
  106.         and return it. Special case: if the user made a custom 
  107.         configurtation, that we cannot represent it will return None """
  108.     if apt_pkg.Config.FindI(softwareproperties.CONF_MAP["autoupdate"]) > 0:
  109.         # Autodownload
  110.         if apt_pkg.Config.FindI(softwareproperties.CONF_MAP["unattended"]) == 1\
  111.            and apt_pkg.Config.FindI(softwareproperties.CONF_MAP["autodownload"]) == 1 :
  112.             return softwareproperties.UPDATE_INST_SEC
  113.         elif apt_pkg.Config.FindI(softwareproperties.CONF_MAP["autodownload"]) == 1 and  \
  114.              apt_pkg.Config.FindI(softwareproperties.CONF_MAP["unattended"]) == 0:
  115.             return softwareproperties.UPDATE_DOWNLOAD
  116.         elif apt_pkg.Config.FindI(softwareproperties.CONF_MAP["unattended"]) == 0 and \
  117.              apt_pkg.Config.FindI(softwareproperties.CONF_MAP["autodownload"]) == 0:
  118.             return softwareproperties.UPDATE_NOTIFY
  119.         else:
  120.             return None
  121.     elif apt_pkg.Config.FindI(softwareproperties.CONF_MAP["unattended"]) == 0 and \
  122.          apt_pkg.Config.FindI(softwareproperties.CONF_MAP["autodownload"]) == 0:
  123.         return softwareproperties.UPDATE_MANUAL
  124.     else:
  125.         return None
  126.  
  127.   def set_update_automation_level(self, state):
  128.     """ Set the apt periodic configurtation to the selected 
  129.         update automation level. To synchronize the cache update and the 
  130.         actual upgrading function, the upgrade function, e.g. unattended, 
  131.         will run every day, if enabled. """
  132.     if state == softwareproperties.UPDATE_INST_SEC:
  133.         apt_pkg.Config.Set(softwareproperties.CONF_MAP["unattended"], str(1))
  134.         apt_pkg.Config.Set(softwareproperties.CONF_MAP["autodownload"], str(1))
  135.     elif state == softwareproperties.UPDATE_DOWNLOAD:
  136.         apt_pkg.Config.Set(softwareproperties.CONF_MAP["autodownload"], str(1))
  137.         apt_pkg.Config.Set(softwareproperties.CONF_MAP["unattended"], str(0))
  138.     elif state == softwareproperties.UPDATE_NOTIFY:
  139.         apt_pkg.Config.Set(softwareproperties.CONF_MAP["autodownload"], str(0))
  140.         apt_pkg.Config.Set(softwareproperties.CONF_MAP["unattended"], str(0))
  141.     else:
  142.         apt_pkg.Config.Set(softwareproperties.CONF_MAP["autoupdate"], str(0))
  143.         apt_pkg.Config.Set(softwareproperties.CONF_MAP["unattended"], str(0))
  144.         apt_pkg.Config.Set(softwareproperties.CONF_MAP["autodownload"], str(0))
  145.     self.set_modified_config()
  146.  
  147.   def set_update_interval(self, days):
  148.       """Set the interval in which we check for available updates"""
  149.       # Only write the key if it has changed
  150.       if not days == apt_pkg.Config.FindI(softwareproperties.CONF_MAP["autoupdate"]):
  151.           apt_pkg.Config.Set(softwareproperties.CONF_MAP["autoupdate"], str(days))
  152.           self.set_modified_config()
  153.  
  154.   def get_update_interval(self):
  155.     """ Returns the interval of the apt periodic cron job """
  156.     return apt_pkg.Config.FindI(softwareproperties.CONF_MAP["autoupdate"])
  157.  
  158.   def get_release_upgrades_policy(self):
  159.     """
  160.     return the release upgrade policy:
  161.      RELASE_UPGRADE_NEVER
  162.      RELASE_UPGRADE_NORMAL
  163.      RELASE_UPGRADE_LTS
  164.     """
  165.     # default (if no option is set) is NORMAL
  166.     if not os.path.exists(self.RELEASE_UPGRADES_CONF):
  167.       return self.RELEASE_UPGRADES_NORMAL
  168.     parser = ConfigParser()
  169.     parser.read(self.RELEASE_UPGRADES_CONF)
  170.     if parser.has_option("DEFAULT","Prompt"):
  171.       type = parser.get("DEFAULT","Prompt").lower()
  172.       for k, v in self.release_upgrades_policy_map.iteritems():
  173.         if v == type:
  174.           return k
  175.     return self.RELEASE_UPGRADES_NORMAL
  176.  
  177.   def set_release_upgrades_policy(self, i):
  178.     """
  179.     set the release upgrade policy:
  180.      RELASE_UPGRADE_NEVER
  181.      RELASE_UPGRADE_NORMAL
  182.      RELASE_UPGRADE_LTS
  183.      """
  184.     # we are note using ConfigParser.write() as it removes comments
  185.     if not os.path.exists(self.RELEASE_UPGRADES_CONF):
  186.       f = open(self.RELEASE_UPGRADES_CONF,"w")
  187.       f.write("[DEFAULT]\nprompt=%s\n"% self.release_upgrades_policy_map[i])
  188.       return True
  189.     f = open(self.RELEASE_UPGRADES_CONF,"r")
  190.     out = NamedTemporaryFile()
  191.     for line in map(string.strip, f.readlines()):
  192.       if line.lower().startswith("prompt"):
  193.         out.write("prompt=%s\n" % self.release_upgrades_policy_map[i])
  194.       else:
  195.         out.write(line+"\n")
  196.     out.flush()
  197.     shutil.copymode(self.RELEASE_UPGRADES_CONF, out.name)
  198.     shutil.copy(out.name, self.RELEASE_UPGRADES_CONF)
  199.     return True
  200.  
  201.   def get_popcon_participation(self):
  202.     """ Will return True if the user wants to participate in the popularity 
  203.         contest. Otherwise it will return False. Special case: if no 
  204.         popcon is installed it will return False """
  205.     if os.path.exists(self.popconfile):
  206.         lines = open(self.popconfile).read().split("\n")
  207.         active = False
  208.         for line in lines:
  209.             try:
  210.                 (key,value) = line.split("=")
  211.                 if key == "PARTICIPATE" and value.strip('"').lower() == "yes":
  212.                     active = True
  213.             except ValueError:
  214.                 continue
  215.         return active
  216.     else:
  217.         return False
  218.  
  219.   def set_popcon_pariticipation(self, is_helpful):
  220.     """ Enable or disable the participation in the popularity contest """
  221.     if is_helpful == True:
  222.         value = "yes"
  223.     else:
  224.         value = "no"
  225.     if os.path.exists(self.popconfile):
  226.         # read the current config and replace the corresponding settings
  227.         # FIXME: should we check the other values, too?
  228.         lines = map(lambda line: re.sub(r'^(PARTICIPATE=)(".+?")',
  229.                                         '\\1"%s"' % value,
  230.                                         line),
  231.                     open(self.popconfile, "r").readlines())
  232.     else:
  233.         # create a new popcon config file
  234.         m = md5.new()
  235.         m.update(open("/dev/urandom", "r").read(1024))
  236.         id = m.hexdigest()
  237.         lines = []
  238.         lines.append("MY_HOSTID=\"%s\"\n" % id)
  239.         lines.append("PARTICIPATE=\"%s\"\n" % str(value))
  240.         lines.append("USE_HTTP=\"yes\"\n")
  241.     open(self.popconfile, "w").writelines(lines)
  242.  
  243.   def get_source_code_state(self):
  244.     """Return True if all distro componets are also available as 
  245.        source code. Otherwise return Flase. Special case: If the
  246.        configuration cannot be represented return None"""
  247.   
  248.     if len(self.distro.source_code_sources) < 1:
  249.         # we don't have any source code sources, so
  250.         # uncheck the button
  251.         self.distro.get_source_code = False
  252.         return False
  253.     else:
  254.         # there are source code sources, so we check the button
  255.         self.distro.get_source_code = True
  256.         # check if there is a corresponding source code source for
  257.         # every binary source. if not set the checkbutton to inconsistent
  258.         templates = {}
  259.         sources = []
  260.         sources.extend(self.distro.main_sources)
  261.         sources.extend(self.distro.child_sources)
  262.         for source in sources:
  263.             if templates.has_key(source.template):
  264.                 for comp in source.comps:
  265.                     templates[source.template].add(comp)
  266.             else:
  267.                 templates[source.template] = set(source.comps)
  268.         # add fake http sources for the cdrom, since the sources
  269.         # for the cdrom are only available in the internet
  270.         if len(self.distro.cdrom_sources) > 0:
  271.             templates[self.distro.source_template] = self.distro.cdrom_comps
  272.         for source in self.distro.source_code_sources:
  273.             if not templates.has_key(source.template) or \
  274.                (templates.has_key(source.template) and not \
  275.                 (len(set(templates[source.template]) ^ set(source.comps)) == 0\
  276.                  or (len(set(source.comps) ^ self.distro.enabled_comps) == 0))):
  277.                 self.distro.get_source_code = False
  278.                 return None
  279.                 break
  280.     return True
  281.  
  282.   def print_source_entry(self, source):
  283.     """Print the data of a source entry to the command line"""
  284.     for (label, value) in [("URI:", source.uri),
  285.                            ("Comps:", source.comps),
  286.                            ("Enabled:", not source.disabled),
  287.                            ("Valid:", not source.invalid)]:
  288.         print " %s %s" % (label, value)
  289.     if source.template:
  290.         for (label, value) in [("MatchURI:", source.template.match_uri),
  291.                                ("BaseURI:", source.template.base_uri)]:
  292.             print " %s %s" % (label, value)
  293.     print "\n"
  294.  
  295.   def massive_debug_output(self):
  296.     """Print the complete sources.list""" 
  297.     print "START SOURCES.LIST:"
  298.     for source in self.sourceslist:
  299.         print source.str()
  300.     print "END SOURCES.LIST\n"
  301.  
  302.   def enable_component(self, comp):
  303.     """Enable a component of the distro"""
  304.     self.distro.enable_component(comp) 
  305.     self.set_modified_sourceslist()
  306.  
  307.   def disable_component(self, comp):
  308.     """Disable a component of the distro"""
  309.     self.distro.disable_component(comp) 
  310.     self.set_modified_sourceslist()
  311.  
  312.   def disable_child_source(self, template):
  313.     """Enable a child repo of the distribution main repository"""
  314.     for source in self.distro.child_sources:
  315.         if source.template == template:
  316.             self.sourceslist.remove(source)
  317.     for source in self.distro.source_code_sources:
  318.         if source.template == template:
  319.             self.sourceslist.remove(source)
  320.     self.set_modified_sourceslist()
  321.  
  322.   def enable_child_source(self, template):
  323.     """Enable a child repo of the distribution main repository"""
  324.     # Use the currently selected mirror only if the child source
  325.     # did not override the server
  326.     if template.base_uri == None:
  327.         child_uri = self.distro.default_server
  328.     else:
  329.         child_uri = template.base_uri
  330.     self.distro.add_source(uri=child_uri, dist=template.name)
  331.     self.set_modified_sourceslist()
  332.  
  333.   def disable_source_code_sources(self):
  334.     """Remove all distro source code sources"""
  335.     sources = []
  336.     sources.extend(self.distro.main_sources)
  337.     sources.extend(self.distro.child_sources)
  338.     # remove all exisiting sources
  339.     for source in self.distro.source_code_sources:
  340.         self.sourceslist.remove(source)
  341.     self.set_modified_sourceslist()
  342.   
  343.   def enable_source_code_sources(self):
  344.     """Enable source code source for all distro sources"""
  345.     sources = []
  346.     sources.extend(self.distro.main_sources)
  347.     sources.extend(self.distro.child_sources)
  348.  
  349.     # remove all exisiting sources
  350.     for source in self.distro.source_code_sources:
  351.         self.sourceslist.remove(source)
  352.  
  353.     for source in sources:
  354.         self.sourceslist.add("deb-src",
  355.                              source.uri,
  356.                              source.dist,
  357.                              source.comps,
  358.                              "Added by software-properties",
  359.                              self.sourceslist.list.index(source)+1,
  360.                              source.file)
  361.     for source in self.distro.cdrom_sources:
  362.         self.sourceslist.add("deb-src",
  363.                              self.distro.source_template.base_uri,
  364.                              self.distro.source_template.name,
  365.                              source.comps,
  366.                              "Added by software-properties",
  367.                              self.sourceslist.list.index(source)+1,
  368.                              source.file)
  369.     self.set_modified_sourceslist()
  370.  
  371.   def backup_sourceslist(self):
  372.     """Store a backup of the source.list in memory"""
  373.     self.sourceslist_backup = []
  374.     for source in self.sourceslist.list:
  375.         source_bkp = SourceEntry(line=source.line,file=source.file)
  376.         self.sourceslist_backup.append(source_bkp)
  377.   
  378.   def toggle_source_use(self, source):
  379.     """Enable or disable the selected channel"""
  380.     #FIXME cdroms need to disable the comps in the childs and sources
  381.     source.disabled = not source.disabled
  382.     self.set_modified_sourceslist()
  383.  
  384.   def revert(self):
  385.     """Revert all settings to the state when software-properties 
  386.        was launched"""
  387.     #FIXME: GPG keys are still missing
  388.     self.restore_apt_conf()
  389.     self.revert_sourceslist()
  390.  
  391.   def revert_sourceslist(self):
  392.     """Restore the source list from the startup of the dialog"""
  393.     self.sourceslist.list = []
  394.     for source in self.sourceslist_backup:
  395.         source_reset = SourceEntry(line=source.line,file=source.file)
  396.         self.sourceslist.list.append(source_reset)
  397.     self.save_sourceslist()
  398.     self.reload_sourceslist()
  399.  
  400.   def set_modified_sourceslist(self):
  401.     """The sources list was changed and now needs to be saved and reloaded"""
  402.     self.modified_sourceslist = True
  403.     if self.options.massive_debug == True:
  404.         self.massive_debug_output()
  405.     self.save_sourceslist()
  406.     self.reload_sourceslist()
  407.  
  408.   def set_modified_config(self):
  409.     """Write the changed apt configuration to file"""
  410.     self.write_config()
  411.  
  412.   def render_source(self, source):
  413.     """Render a nice output to show the source in a treeview"""
  414.     if source.template == None:
  415.         if source.comment:
  416.             contents = "<b>%s</b>" % escape(source.comment)
  417.             # Only show the components if there are more than one
  418.             if len(source.comps) > 1:
  419.                 for c in source.comps:
  420.                     contents += " %s" % c
  421.         else:
  422.             contents = "<b>%s %s</b>" % (source.uri, source.dist)
  423.             for c in source.comps:
  424.                 contents += " %s" % c
  425.         if source.type in ("deb-src", "rpm-src"):
  426.             contents += " %s" % _("(Source Code)")
  427.         return contents
  428.     else:
  429.         # try to make use of a corresponding template
  430.         contents = "<b>%s</b>" % source.template.description
  431.         if source.type in ("deb-src", "rpm-src"):
  432.             contents += " (%s)" % _("Source Code")
  433.         if source.comment:
  434.             contents +=" %s" % source.comment
  435.         if source.template.child == False:
  436.             for comp in source.comps:
  437.                 if source.template.has_component(comp):
  438.                     # fixme: move something like this into distinfo.Template
  439.                     #        (why not use a dictionary again?)
  440.                     for c in source.template.components:
  441.                         if c.name == comp:
  442.                             contents += "\n%s" % c.description
  443.                 else:
  444.                     contents += "\n%s" % comp
  445.         return contents
  446.  
  447.   def get_comparable(self, source):
  448.       """extract attributes to sort the sources"""
  449.       cur_sys = 1
  450.       has_template = 1
  451.       has_comment = 1
  452.       is_source = 1
  453.       revert_numbers = string.maketrans("0123456789", "9876543210")
  454.       if source.template:
  455.         has_template = 0
  456.         desc = source.template.description
  457.         if source.template.distribution == self.distro:
  458.             cur_sys = 0
  459.       else:
  460.           desc = "%s %s %s" % (source.uri, source.dist, source.comps)
  461.           if source.comment:
  462.               has_comment = 0
  463.       if source.type.find("src"):
  464.           is_source = 0
  465.       return (cur_sys, has_template, has_comment, is_source,
  466.               desc.translate(revert_numbers))
  467.  
  468.   def get_isv_sources(self):
  469.     """Return a list of sources that are not part of the distribution"""
  470.     isv_sources = []
  471.     for source in self.sourceslist.list:
  472.         if not source.invalid and\
  473.            (source not in self.distro.main_sources and\
  474.             source not in self.distro.cdrom_sources and\
  475.             source not in self.distro.child_sources and\
  476.             source not in self.distro.disabled_sources) and\
  477.            source not in self.distro.source_code_sources:
  478.             isv_sources.append(source)
  479.     return isv_sources
  480.  
  481.   def get_cdrom_sources(self):
  482.     """Return the list of CDROM based distro sources"""
  483.     return self.distro.cdrom_sources
  484.       
  485.   def get_comp_download_state(self, comp):
  486.     """Return a tuple: the first value describes if a component is enabled
  487.        in the Internet repositories. The second value describes if the
  488.        first value is inconsistent."""
  489.     #FIXME: also return a correct inconsistent value
  490.     return (comp.name in self.distro.download_comps, False)
  491.  
  492.   def get_comp_child_state(self, template):
  493.     """Return a tuple: the first value describes if a component is enabled
  494.        in one of the child source that matcth the given template. 
  495.        The second value describes if the first value is inconsistent."""
  496.     comps = []
  497.     for child in self.distro.child_sources:
  498.         if child.template == template:
  499.             comps.extend(child.comps)
  500.     if len(comps) > 0 and \
  501.         len(self.distro.enabled_comps ^ set(comps)) == 0:
  502.         # All enabled distro components are also enabled for the child source
  503.         return (True, False)
  504.     elif len(comps) > 0 and\
  505.         len(self.distro.enabled_comps ^ set(comps)) != 0:
  506.         # A matching child source does exist but doesn't include all 
  507.         # enabled distro components
  508.         return(False, True)
  509.     else:
  510.         # There is no corresponding child source at all
  511.         return (False, False)
  512.   
  513.   def reload_sourceslist(self):
  514.     self.sourceslist.refresh()
  515.     self.sourceslist_visible=[]
  516.     self.distro.get_sources(self.sourceslist)    
  517.  
  518.   def write_config(self):
  519.     """Write the current apt configuration to file"""
  520.     # update the adept file as well if it is there
  521.     conffiles = ["/etc/apt/apt.conf.d/10periodic",
  522.                  "/etc/apt/apt.conf.d/15adept-periodic-update"]
  523.  
  524.     # check (beforehand) if one exists, if not create one
  525.     for f in conffiles:
  526.       if os.path.isfile(f):
  527.         break
  528.     else:
  529.       print "No config found, creating one"
  530.       open(conffiles[0], "w")
  531.  
  532.     # now update them
  533.     for periodic in conffiles:
  534.       # read the old content first
  535.       content = []
  536.       if os.path.isfile(periodic):
  537.         content = open(periodic, "r").readlines()
  538.         cnf = apt_pkg.Config.SubTree("APT::Periodic")
  539.  
  540.         # then write a new file without the updated keys
  541.         f = open(periodic, "w")
  542.         for line in content:
  543.           for key in cnf.List():
  544.             if line.find("APT::Periodic::%s" % (key)) >= 0:
  545.               break
  546.           else:
  547.             f.write(line)
  548.  
  549.         # and append the updated keys
  550.         for i in cnf.List():
  551.           f.write("APT::Periodic::%s \"%s\";\n" % (i, cnf.FindI(i)))
  552.         f.close()    
  553.  
  554.   def save_sourceslist(self):
  555.     """Backup the existing sources.list files and write the current 
  556.        configuration"""
  557.     self.sourceslist.backup(".save")
  558.     self.sourceslist.save()
  559.  
  560.   def add_source_from_line(self, line):
  561.     """Add a source with the given apt line"""
  562.     self.sourceslist.list.append(SourceEntry(line))
  563.     self.set_modified_sourceslist()
  564.  
  565.   def remove_source(self, source):
  566.     """Remove the given source"""
  567.     self.sourceslist.remove(source)
  568.     self.set_modified_sourceslist()
  569.  
  570.   def add_key(self, path):
  571.     """Add a gnupg key to the list of trusted software vendors"""
  572.     if not os.path.exists(path):
  573.         return False
  574.     try:
  575.         self.apt_key.add(path)
  576.         return True
  577.     except:
  578.         return False
  579.  
  580.   def remove_key(self, fingerprint):
  581.     """Remove a gnupg key from the list of trusted software vendors"""
  582.     try:
  583.         self.apt_key.rm(fingerprint)
  584.         return True
  585.     except:
  586.         return False
  587.  
  588.  
  589.  
  590. if __name__ == "__main__":
  591.   sp = SoftwareProperties()
  592.   print sp.get_release_upgrades_policy()
  593.   sp.set_release_upgrades_policy(0)
  594.